﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;
using System.Xml.Serialization;
using SharpSvn;

namespace SvnFlow
{
    public class BranchesFile
    {
        internal const string DefaultPropertiesAllowedToMerge =
            "svn:executable,svn:mime-type,svn:ignore,svn:keywords,svn:eol-style,svn:special,svn:needs-lock";

        internal readonly Branches m_branches;
        internal readonly string m_fileName;

        public string FileName { get { return m_fileName; } }

        public List<FolderMap> MappedFolders { get { return m_branches.MappedFolders; } }

        public string[] MergeSvnProperties
        {
            get
            {
                return (m_branches != null && m_branches.MergeSvnProperties != null) ?
                        m_branches.MergeSvnProperties.Split(new[] { ',' }) : new string[] { };
            }
        }

        public BranchesFile(string fileName)
        {
            m_fileName = fileName;

            if (!File.Exists(FileName))
            {
                throw new ArgumentException(string.Format("Config file {0} not found.", fileName), "fileName");
            }

            using (var rs = File.OpenRead(FileName))
            {
                m_branches = (Branches)new XmlSerializer(typeof(Branches)).Deserialize(rs);
            }

            CheckBranch(m_branches, "left");
            CheckBranch(m_branches, "right");
        }

        public void Save(string fileName)
        {
            Save(m_branches, fileName);
        }

        public void Save()
        {
            Save(FileName);
        }

        public static void CreateEmpty(string fileName, DateTime currentTime, CmdOptions options)
        {
            var emptyConfigText = Properties.Resources.EmptyConfig;

            var leftTime = currentTime;
            var rightTime = currentTime;
            var leftPath = "";
            var rightPath = "";
            var leftRevision = SvnRevision.Zero;
            var rightRevision = SvnRevision.Zero;
            if(options != null && options.GeneratePath.Count() != 0)
            {
                SvnInfoEventArgs info;
                var client = new SvnClient();
                client.GetInfo(new SvnPathTarget(options.GeneratePath[0]),out info);
                leftPath = info.Uri.ToString();
                leftRevision = info.LastChangeRevision;
                leftTime = info.LastChangeTime;
                client.GetInfo(new SvnPathTarget(options.GeneratePath[1]), out info);
                rightPath = info.Uri.ToString();
                rightRevision = info.LastChangeRevision;
                rightTime = info.LastChangeTime;
            }

            emptyConfigText = emptyConfigText.Replace("%%lefturi%%", leftPath);
            emptyConfigText = emptyConfigText.Replace("%%righturi%%", rightPath);
            emptyConfigText = emptyConfigText.Replace("%%MergeSvnProperties%%", DefaultPropertiesAllowedToMerge);
            emptyConfigText = emptyConfigText.Replace("%%FolderMapLeft%%", "");
            emptyConfigText = emptyConfigText.Replace("%%FolderMapRight%%", "");
            emptyConfigText = emptyConfigText.Replace("%%HistoryItemTimeLeft%%", leftTime.ToString(SvnHelper.SerializedTimeFormat));
            emptyConfigText = emptyConfigText.Replace("%%HistoryItemLastMergedRevisionLeft%%", leftRevision.ToString());
            emptyConfigText = emptyConfigText.Replace("%%HistoryItemTimeRight%%", rightTime.ToString(SvnHelper.SerializedTimeFormat));
            emptyConfigText = emptyConfigText.Replace("%%HistoryItemLastMergedRevisionRight%%", rightRevision.ToString());
            File.WriteAllText(fileName, emptyConfigText);

            Logger.WriteLine("Empty configuration file {0} created.", fileName);
        }

        public BranchInfo CreateBranchInfo(string name)
        {
            try
            {
                var revision = GetRevisionRange(name);

                return new BranchInfo(
                    new Uri(m_branches[name].Uri), name, Path.Combine(Path.GetDirectoryName(FileName), name),
                    revision);
            }
            catch (Exception e)
            {
                Logger.WriteLine("Error in {0} branch settings: {1}", name, e.Message);
                return null;
            }
        }

        public void AddHistoryItem(DateTime now, string fromName, string toName, SvnRevisionRange range)
        {
            m_branches.History.Add(
                new HistoryItem
                    {
                        ParsedTime = now,
                        FromBranch = fromName,
                        LastMergedRevision = range.EndRevision.Revision
                    });
        }

        private static void CheckBranch(Branches branches, string branchName)
        {
            if (branches == null)
            {
                throw new ArgumentNullException("Branches");
            }
            var branch = branches[branchName];
            if (branch == null)
            {
                throw new ArgumentNullException(branchName,String.Format("Branch {0} is missing.", branchName));
            }
            if (string.IsNullOrEmpty(branch.Uri))
            {
                throw new ArgumentNullException(branchName,String.Format("Uri in branch {0} is null or empty", branchName));
            }
            if (!Uri.IsWellFormedUriString(branch.Uri, UriKind.RelativeOrAbsolute))
            {
                throw new ArgumentException(String.Format("Uri in branch {0} not valid", branchName), branchName);
            }
            if (!new Regex(".+/$").IsMatch(branch.Uri))
                branch.Uri += "/";
        }

        private SvnRevisionRange GetRevisionRange(string branchName)
        {
            var historyItems = from item in m_branches.History
                               where item.FromBranch == branchName
                               orderby item.ParsedTime descending
                               select item;

            var historyItem = historyItems.FirstOrDefault();
            return historyItem != null ?
                new SvnRevisionRange(SvnRevision.Base, historyItem.LastMergedRevision) : 
                new SvnRevisionRange(SvnRevision.Zero, SvnRevision.Zero);
        }

        private static void Save(Branches branches, string fileName)
        {
            if (String.IsNullOrEmpty(fileName))
                throw new ArgumentNullException(fileName);
            if (branches == null)
                throw new ArgumentNullException("branches");

            using (var fs = File.Create(fileName))
                new XmlSerializer(typeof(Branches)).Serialize(fs, branches);
        }
    }
}
